package com.yugao.fintech.antelope.shop.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import com.yugao.fintech.antelope.base.security.utils.SecurityUtils;
import com.yugao.fintech.antelope.base.model.constants.TenantCons;
import com.yugao.fintech.antelope.base.model.module.auth.LoginUser;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContext;
import com.yugao.fintech.antelope.base.model.module.tenant.TenantContextHolder;
import com.yugao.fintech.antelope.shop.assembly.GoodsAssembly;
import com.yugao.fintech.antelope.shop.assembly.SkuStockAssembly;
import com.yugao.fintech.antelope.shop.manager.*;
import com.yugao.fintech.antelope.shop.mapper.GoodsMapper;
import com.yugao.fintech.antelope.shop.model.constants.StockInOutStatus;
import com.yugao.fintech.antelope.shop.model.constants.StockInOutType;
import com.yugao.fintech.antelope.shop.model.rqrs.sku.SkuBase;
import com.yugao.fintech.antelope.shop.model.rqrs.sku.SkuReq;
import com.yugao.fintech.antelope.shop.model.rqrs.sku.SkuResp;
import com.yugao.fintech.antelope.shop.service.GoodsService;
import com.yugao.fintech.antelope.shop.service.StoreService;
import com.yugao.fintech.antelope.base.tenant.core.utils.TenantUtils;
import com.yugao.fintech.framework.assistant.utils.CollectionUtils;
import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import com.yugao.fintech.framework.core.utils.SpringUtils;
import com.yugao.fintech.framework.mybatisplus.utils.PageUtil;
import com.yugao.fintech.framework.validation.ValidationUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Validator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 商品
 *

 */
@Slf4j
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods>
    implements IService<Goods>, GoodsService {

    @Autowired
    private StoreGoodsManager storeGoodsManager;
    @Autowired
    private GoodsAssembly goodsAssembly;
    @Autowired
    private GoodsSpecValueManager goodsSpecValueManager;
    @Autowired
    private SkuStockAssembly skuStockAssembly;
    @Autowired
    private SkuStockManager skuStockManager;
    @Autowired
    private SkuStockInOutRecordManager stockInOutRecordManager;
    @Autowired
    private SkuStockInOutDetailManager stockInOutDetailManager;
    @Autowired
    private GoodsCategoryManager categoryManager;
    @Autowired
    private StoreManager storeManager;
    @Autowired
    private Validator validator;
    @Autowired
    private StoreService storeService;
    
    @Override
    public IPage<GoodsResp> pageGoods(GoodsPageReq req) {
        TenantContext tenantContext = TenantContextHolder.get();
        Long tenantId = tenantContext.getTenantId();

        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<Goods> lqw = Goods.lqw()
            .ge(Objects.nonNull(beginTime), Goods::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), Goods::getCreatedAt, endTime)
            .like(StringUtils.isNotEmpty(req.getKeyword()), Goods::getName, req.getKeyword())
            .orderByDesc(Goods::getCreatedAt);
        IPage<Goods> pageData = this.page(PageUtil.to(req), lqw);
        List<Goods> records = pageData.getRecords();

        // 获取添加到门店信息
        List<Long> goodsIds = records.stream().map(Goods::getId).collect(Collectors.toList());

        // 获取商品和租户(门店)的上架(映射)关系
        Map<Long, List<StoreGoods>> goodsIdAndTenantIdMapper = TenantUtils.execute(TenantCons.Type.MAIN, tenantContext.getMainTenantId(), () -> {
            if (CollUtil.isEmpty(goodsIds)) {
                return Collections.emptyMap();
            }
            List<StoreGoods> storeGoods = this.storeGoodsManager.listByGoodsIds(goodsIds, null);
            return storeGoods.stream()
                    .collect(Collectors.groupingBy(StoreGoods::getGoodsId));
        });

        List<Long> categoryIds = records.stream().map(Goods::getGoodsCategoryId).distinct().collect(Collectors.toList());
        Map<Long, String> categoryMap = CollectionUtils.isEmpty(categoryIds) ?
                Collections.emptyMap() : categoryManager.listByIds(categoryIds).stream().collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getName));

        return PageUtil.to(pageData, (e) -> {
            GoodsResp resp = goodsAssembly.toGoodsResp(e);
            List<StoreGoods> storeGoodsList = goodsIdAndTenantIdMapper.getOrDefault(e.getId(), Collections.emptyList());
            boolean isAddCurrentStore = storeGoodsList.stream().anyMatch(e1 -> e1.getTenantId().compareTo(tenantId) == 0);
            resp.setIsAddCurrentStore(isAddCurrentStore);
            resp.setCategoryName(categoryMap.get(e.getGoodsCategoryId()));
            return resp;
        });
    }

    @Override
    public IPage<GoodsResp> pageCurrentStoreGoods(GoodsPageReq req) {
        TenantContext tenantContext = TenantContextHolder.get();
        Long tenantId = tenantContext.getTenantId();

        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        IPage<Goods> pageData = this.baseMapper.pageStoreGoods(PageUtil.to(req), beginTime, endTime, req.getKeyword(), tenantId);
        List<Goods> records = pageData.getRecords();

        List<Long> categoryIds = records.stream().map(Goods::getGoodsCategoryId).distinct().collect(Collectors.toList());
        Map<Long, String> categoryMap = CollectionUtils.isEmpty(categoryIds) ?
                Collections.emptyMap() : categoryManager.listByIds(categoryIds).stream().collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getName));

        return PageUtil.to(pageData, (e) -> {
            GoodsResp resp = goodsAssembly.toGoodsResp(e);
            resp.setIsAddCurrentStore(true);

            // 如果非总店, 就判断商品所在具体门店的发布状态
            resp.setStatus(e.getIsPublish() ? Goods.Status.LISTED : Goods.Status.NO_LISTED);
            resp.setCategoryName(categoryMap.get(e.getGoodsCategoryId()));
            return resp;
        });
    }

    @Override
    public GoodsResp getGoods(Long id) {
        Goods entity = this.getById(id);
        if (Objects.isNull(entity)) {
            throw new BizException("商品不存在");
        }

        // 查找spec数据
        LambdaQueryWrapper<GoodsSpecValue> specLqw = GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, entity.getId());
        List<GoodsSpecValue> specList = goodsSpecValueManager.list(specLqw);

        // 查找sku数据
        List<SkuStock> skuList;
        try {
            skuList = TenantUtils.executeIgnore(TenantCons.Type.SUB, () ->
                    skuStockManager.list(SkuStock.lqw().eq(SkuStock::getGoodsId, entity.getId()))
            );
        } catch (Exception e) {
            throw new BizException("查询sku失败");
        }

        // 按照sku进行划分
        Map<String, List<SkuStock>> skuMap = skuList.stream().collect(Collectors.groupingBy(e -> e.getSpecData().toString()));

        List<SkuResp> skuRespList = new ArrayList<>();
        skuMap.forEach((k, v) -> {
            SkuStock skuStock = v.get(0);
            SkuResp skuResp = skuStockAssembly.toSkuResp(skuStock);

            List<SkuBase.Stock> stockList = v.stream().map(e -> {
                SkuBase.Stock stock = new SkuBase.Stock();
                stock.setStock(e.getStock());
                stock.setStoreId(e.getTenantId());
                return stock;
            }).collect(Collectors.toList());
            skuResp.setStockList(stockList);
            skuRespList.add(skuResp);
        });

        GoodsResp resp = goodsAssembly.toGoodsResp(entity);
        resp.setSpecList(specList);
        resp.setSkuList(skuRespList);
        return resp;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveGoods(GoodsSaveReq req) {
        LoginUser loginUser = SecurityUtils.getLoginUser();


        ValidationUtils.validate(validator, req, req.getMultiSpec() ? MultiSpecGoods.class : UnifySpecGoods.class);
        Goods goods = goodsAssembly.toGoods(req);

        // 处理商品
        boolean ret = this.saveOrUpdate(goods);

        List<SkuReq> skuList = req.getSkuList();

        // step 处理商品规格信息
        if (req.getMultiSpec()) {
            GoodsServiceImpl thisProxy = SpringUtils.getAopProxy(this);
            thisProxy.saveGoodsSpecValue(goods, skuList);
        }

        // step 处理商品sku库存信息
        List<ShopStore> storeList = storeManager.list();
        this.saveSkuStock(SecurityUtils.getLoginUser(), storeList, goods, req.getSkuList());

    	return ret;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateGoods(GoodsUpdateReq req) {
        Goods entity = goodsAssembly.toGoods(req);

        // step 获取数据库中保存的库存规格
        List<SkuStock> skuStockList = TenantUtils.execute(
                TenantCons.Type.MAIN,
                TenantContextHolder.get().getMainTenantId(),
                () -> skuStockManager.list(SkuStock.lqw().eq(SkuStock::getGoodsId, req.getId()))
        );

        // step 解析接收的规格值
        LambdaQueryWrapper<GoodsSpecValue> specValueLqw = GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, req.getId());
        List<GoodsSpecValue> specValueList = goodsSpecValueManager.list(specValueLqw);
        Set<String> oldSpecValues = specValueList.stream().flatMap(e -> e.getValues().stream()).collect(Collectors.toSet());
        Set<String> newSpecValues = req.getSpecList().stream().flatMap(e -> e.getValues().stream()).collect(Collectors.toSet());

        // step 获取被删除的规格
//        Set<String> deletedSpecValues = new HashSet<>(oldSpecValues);
//        deletedSpecValues.removeAll(newSpecValues);

        // step 如果被删除的规格, 库存数大于0, 则报错提示
//        Integer deletedSpecTotalStock = skuStockList.stream()
//                .filter(e -> e.getSpecData().stream().anyMatch(e1 -> deletedSpecValues.contains(e1.getValue())))
//                .map(SkuStock::getStock)
//                .reduce(0, Integer::sum);
//
//        if (deletedSpecTotalStock > 0) {
//            throw new BizException("需要商品所有规格的库存都为0时，才能删除规格。如需添加，请先调整库存");
//        }

        // step 获取新增的规格, 满足所有规格库存为0时才能添加规格
//        Set<String> addSpecValues = new HashSet<>(newSpecValues);
//        addSpecValues.removeAll(oldSpecValues);
//        if (CollectionUtils.isNotEmpty(addSpecValues)) {
//            // 计算库存总值
//            Integer totalStock = skuStockList.stream().map(SkuStock::getStock).reduce(0, Integer::sum);
//            if (totalStock > 0) {
//                throw new BizException("需要商品所有规格的库存都为0时，才能添加规格。如需添加，请先调整库存");
//            }
//        }

        // step 删除商品规格信息并重新创建
        GoodsServiceImpl thisProxy = SpringUtils.getAopProxy(this);
        if (req.getMultiSpec()) {
            goodsSpecValueManager.remove(GoodsSpecValue.lqw().eq(GoodsSpecValue::getGoodsId, req.getId()));
            thisProxy.saveGoodsSpecValue(entity, req.getSkuList());
        }

        // step 删除sku信息并重新创建
        List<ShopStore> storeList = storeManager.list();
        skuStockManager.deleteByGoodsId(entity.getId());
        thisProxy.saveSkuStock(SecurityUtils.getLoginUser(), storeList, entity, req.getSkuList());
    	return this.updateById(entity);
    }

    public void saveGoodsSpecValue(Goods goods, List<SkuReq> skuList ) {
        List<GoodsSpecValue> specValueList = new ArrayList<>();
        Map<Long, List<SkuStock.Spec>> specValueMap = skuList.stream().flatMap(e -> e.getSpecData().stream())
                .collect(Collectors.groupingBy(SkuStock.Spec::getSpecId));
        specValueMap.forEach((k, v) -> {
            GoodsSpecValue specValue = new GoodsSpecValue();
            String specName = v.stream().findFirst().map(SkuStock.Spec::getKey).orElse("");
            specValue.setValues(v.stream().map(SkuStock.Spec::getValue).distinct().collect(Collectors.toList()))
                    .setGoodsId(goods.getId()).setSpecId(k).setName(specName);
            specValueList.add(specValue);
        });
        goodsSpecValueManager.saveBatch(specValueList);
        log.info("batch save spec value, size: {}, goodsId: {}", specValueList.size(), goods.getId());
    }


    public void saveSkuStock(LoginUser loginUser, List<ShopStore> storeList,
                                                  Goods goods, List<SkuReq> skuList) {

        Map<Long, List<SkuStock>> skuStockMap = skuList.stream().flatMap(e -> {
            // 循环保存每个门店sku库存
            if (CollectionUtils.isEmpty(e.getStockList())) {
                return storeList.stream().map(stock -> {
                    SkuStock skuStock = skuStockAssembly.toSku(e).setGoodsId(goods.getId())
                            .setId(YitIdHelper.nextId()).setStock(0);
                    skuStock.setTenantId(stock.getTenantId());
                    return skuStock;
                }).collect(Collectors.toList()).stream();
            }

            return e.getStockList().stream().map(stock -> {
                SkuStock skuStock = skuStockAssembly.toSku(e).setGoodsId(goods.getId())
                        .setId(YitIdHelper.nextId()).setStock(ObjectUtils.defaultIfNull(stock.getStock(), 0));
                skuStock.setTenantId(stock.getStoreId());
                return skuStock;
            }).collect(Collectors.toList()).stream();
        }).collect(Collectors.groupingBy(SkuStock::getTenantId));

        // 保存数据
        skuStockMap.forEach((k, v) -> TenantUtils.execute(k, () ->  {
            // 分批保存每个门店sku库存数据
            skuStockManager.saveBatch(v);
            log.info("batch save sku stock, size: {}, goodsId: {}", v.size(), goods.getId());

            // step 处理商品出入库记录
            // 计算成本金额
            BigDecimal totalCostPrice = v.stream().map(e -> e.getCostPrice().multiply(new BigDecimal(e.getStock())))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            SkuStockInOutRecord inOutRecord = new SkuStockInOutRecord().setStatus(StockInOutStatus.DO_EXAMINE)
                    .setStockNo(String.valueOf(YitIdHelper.nextId())).setOperateTime(new Date())
                    .setAuditTime(new Date()).setTotalPrice(totalCostPrice).setType(StockInOutType.IN_INIT.getKey())
                    .setOperaterId(loginUser.getUserId()).setOperaterName(loginUser.getUserName());
            stockInOutRecordManager.save(inOutRecord);
            log.info("save stock in record, size: {}, goodsId: {}", v.size(), goods.getId());

            // step 处理商品出入库详情
            List<SkuStockInOutDetail> inOutDetailList = v.stream().map(e ->
                    new SkuStockInOutDetail().setSkuId(e.getId()).setGoodsId(goods.getId())
                            .setStockRecordId(inOutRecord.getId()).setChangeCount(e.getStock())
                            .setCostPrice(e.getCostPrice()).setTotalCostPrice(totalCostPrice)
                            .setOperaterId(loginUser.getUserId()).setOperaterName(loginUser.getUserName())
                            .setOperaterTime(new Date())
            ).filter(e -> e.getChangeCount() > 0).collect(Collectors.toList());
            stockInOutDetailManager.saveBatch(inOutDetailList);
            log.info("save stock in detail, size: {}, goodsId: {}", inOutDetailList.size(), goods.getId());
        }));
    }

    @Override
    public boolean deleteGoods(Long id) {
    	return this.removeById(id);
    }
    
    @Override
    public boolean batchDeleteGoods(List<Long> ids) {
    	return this.removeByIds(ids);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePublishStatus(Long goodsId, Set<String> tenantIds) {
        Long mainTenantId = TenantContextHolder.get().getMainTenantId();
        Long tenantId = TenantContextHolder.get().getTenantId();

        String status = Goods.Status.LISTED;
        if (CollUtil.isEmpty(tenantIds)) {
            status = Goods.Status.NO_LISTED;
        }
        LambdaUpdateWrapper<Goods> luw = Goods.luw().eq(Goods::getId, goodsId)
                .set(Goods::getTenantIds, JSON.toJSONString(tenantIds))
                .set(Goods::getStatus, status);
        this.update(luw);

        // 处理商品和门店之间的关系
        List<StoreGoods> allStoreGoods = TenantUtils.execute(
                TenantCons.Type.MAIN,
                mainTenantId, () -> storeGoodsManager.list(StoreGoods.lqw().eq(StoreGoods::getGoodsId, goodsId))
        );

        Set<String> oldStoreGoodsTenantIds = allStoreGoods.stream().map(e -> String.valueOf(e.getTenantId())).collect(Collectors.toSet());
        // 新增发布的门店
        Set<String> newPublishSet = new HashSet<>(tenantIds);
        newPublishSet.removeAll(oldStoreGoodsTenantIds);

        // 插入或更新数据
        for (String item : oldStoreGoodsTenantIds) {
            TenantUtils.execute(Long.parseLong(item), () -> {
                LambdaUpdateWrapper<StoreGoods> storeGoodsLqw = StoreGoods.luw().eq(StoreGoods::getGoodsId, goodsId)
                        .set(StoreGoods::getIsPublish, tenantIds.contains(item));
                storeGoodsManager.update(storeGoodsLqw);
            });
        }

        for (String newPublishTenantId : newPublishSet) {
            TenantUtils.execute(Long.parseLong(newPublishTenantId), () -> {
                StoreGoods storeGoods = new StoreGoods(null, true, goodsId, null);
                storeGoodsManager.save(storeGoods);
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStoreGoodsPublishStatus(List<Long> goodsIds, Boolean isPublish) {
        TenantContext tenantContext = TenantContextHolder.get();
        String tenantId = String.valueOf(tenantContext.getTenantId());
        if (Objects.equals(tenantContext.getMainTenantId(), tenantContext.getTenantId())) {
            throw new BizException("请切换到子门店进行操作");
        }

        List<Goods> goods = this.listByIds(goodsIds);
        storeGoodsManager.deleteByGoodIds(goodsIds);
        goods.forEach(e -> {
            Set<String> tenantIds = Optional.ofNullable(e.getTenantIds()).orElse(new HashSet<>());
            tenantIds.remove(tenantId);
            if (isPublish) {
                e.setStatus(Goods.Status.LISTED);
                tenantIds.add(tenantId);
            }
            e.setTenantIds(tenantIds);

            // 如果没有上架到任何门店, 则设置成已下架状态
            if (CollUtil.isEmpty(tenantIds)) {
                e.setStatus(Goods.Status.NO_LISTED);
            }

            TenantUtils.execute(Long.parseLong(tenantId), () -> {
                StoreGoods storeGoods = new StoreGoods(null, isPublish, e.getId(), null);
                storeGoodsManager.save(storeGoods);
            });
        });

        this.saveOrUpdateBatch(goods);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchAddIntoCurrentStore(Set<Long> goodsIds) {
        TenantContext tenantContext = TenantContextHolder.get();
        if (Objects.equals(tenantContext.getMainTenantId(), tenantContext.getTenantId())) {
            throw new BizException("请切换到子门店进行操作");
        }

        ShopStore shopStore = storeService.getTenantRefStore(tenantContext.getTenantId());
        if (Objects.isNull(shopStore)) {
            throw new BizException("未查找到门店信息");
        }

        List<StoreGoods> dbStoreGoodsList = storeGoodsManager.listByGoodsIds(new ArrayList<>(goodsIds) , null);
        if (dbStoreGoodsList.size() > 0) {
            throw new BizException("已有商品添加到本店, 无需重复添加");
        }

        List<StoreGoods> storeGoods = goodsIds.stream()
                .map(e -> new StoreGoods(null, false, e, null))
                .collect(Collectors.toList());
        storeGoodsManager.saveBatch(storeGoods);

        // 更新商品上线的门店字段
        for (Long goodsId : goodsIds) {
            TenantUtils.execute(TenantCons.Type.MAIN, tenantContext.getMainTenantId(), () -> {
                List<Long> tenantIds = storeGoodsManager.listByGoodsId(goodsId, true)
                        .stream().map(StoreGoods::getTenantId).collect(Collectors.toList());

                if (CollUtil.isEmpty(tenantIds)) {
                    return;
                }
                LambdaUpdateWrapper<Goods> luw = Goods.luw().eq(Goods::getId, goodsId)
                        .set(Goods::getTenantIds, JSON.toJSONString(tenantIds))
                        .set(Goods::getStatus, Goods.Status.LISTED);
                this.update(luw);
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchDeleteFromCurrentStore(List<Long> goodsIds) {
        Long tenantId = TenantContextHolder.get().getTenantId();
        List<Goods> goods = this.listByIds(goodsIds);
        goods.forEach(e -> {
            Set<String> tenantIds = Optional.ofNullable(e.getTenantIds()).orElse(new HashSet<>());
            tenantIds.remove(String.valueOf(tenantId));
            e.setTenantIds(tenantIds);
            if (CollUtil.isEmpty(tenantIds)) {
                e.setStatus(Goods.Status.NO_LISTED);
            }
        });
        this.updateBatchById(goods);
        storeGoodsManager.deleteByGoodIds(goodsIds);
    }
}
